package dk.itu.mario.level;

import java.util.ArrayList;
import java.util.Random;

import dk.itu.mario.MarioInterface.Constraints;
import dk.itu.mario.MarioInterface.GamePlay;
import dk.itu.mario.MarioInterface.LevelInterface;
import dk.itu.mario.engine.sprites.SpriteTemplate;
import dk.itu.mario.engine.sprites.Enemy;


public class MyLevel extends Level{
	//Store information about the level
	public   int ENEMIES = 0; //the number of enemies the level contains
	public   int BLOCKS_EMPTY = 0; // the number of empty blocks
	public   int BLOCKS_COINS = 0; // the number of coin blocks
	public   int BLOCKS_POWER = 0; // the number of power blocks
	public   int COINS = 0; //These are the coins in boxes that Mario collect

	public ArrayList<Block> blocks;
	public ArrayList<ArrayList<Decoration>> decorations;
	 
	private static Random levelSeedRandom = new Random();
	public static long lastSeed;

	public Random random;

	private int difficulty;
	private int type;
	private int gaps;
	
	public long seed;
	

	public MyLevel(int width, int height)
	{
		super(width, height);
		blocks = new ArrayList<Block>();
		decorations = new ArrayList<ArrayList<Decoration>>();
	}


	public MyLevel(int width, int height, long seed, int difficulty, int type, GamePlay playerMetrics)
	{
		this(width, height);
		
		this.seed = seed;
		create(seed, difficulty, type, playerMetrics, false, null, null);
	}
	
	public MyLevel(int width, int height, long seed, int difficulty, int type, GamePlay playerMetrics, ArrayList<Block> blocks, ArrayList<ArrayList<Decoration>> decorations) {
		this(width, height);
		
		this.seed = seed;
		create(seed, difficulty, type, playerMetrics, true, blocks, decorations);
	}

	public void create(long seed, int difficulty, int type, GamePlay playerMetrics, boolean useBlocks, ArrayList<Block> blocks, ArrayList<ArrayList<Decoration>> decorations)
	{
		this.type = type;
		this.difficulty = difficulty;

		lastSeed = seed;
		random = new Random(seed);

		//create the start location
		int length = 0;
		length += buildStraight(0, width, 0, false, true);

		//construct weights for the inital level and pieces
		constructWeights(playerMetrics);

		if(!useBlocks) {
			this.blocks.clear();
			this.decorations.clear();
	
			int blockIndex = 0;
			
			//create all of the medium sections
			while (length < width - 64)
			{
				this.decorations.add(new ArrayList<Decoration>());
				blockIndex = this.decorations.size()-1;
				//if(blockIndex !=2)
				length += buildRandomBlock(length, width - length, blockIndex, false);
			}
		}
		else {
			this.blocks = blocks;
			this.decorations = decorations;
			
			int blockIndex = 0;
			while (length < width - 64 && blockIndex < blocks.size())
			{
				Block b = blocks.get(blockIndex);
				b.blockStart = length;
				
				int thisLength = buildBlock(blocks.get(blockIndex), length, width - length, blockIndex);
				b.blockLength = thisLength;
				length += thisLength;
				blockIndex++;
			}
		}

		//set the end piece
		int floor = height - 1 - random.nextInt(4);

		xExit = length + 8;
		yExit = floor;

		// fills the end piece
		for (int x = length; x < width; x++)
		{
			for (int y = 0; y < height; y++)
			{
				if (y >= floor)
				{
					setBlock(x, y, GROUND);
				}
			}
		}

		if (type == LevelInterface.TYPE_CASTLE || type == LevelInterface.TYPE_UNDERGROUND)
		{
			int ceiling = 0;
			int run = 0;
			for (int x = 0; x < width; x++)
			{
				if (run-- <= 0 && x > 4)
				{
					ceiling = random.nextInt(4);
					run = random.nextInt(4) + 4;
				}
				for (int y = 0; y < height; y++)
				{
					if ((x > 4 && y <= ceiling) || x < 1)
					{
						setBlock(x, y, GROUND);
					}
				}
			}
		}

		fixWalls(0, width);
//		for(int i =0; i < decorations.size()/2;i++) {
//			undecorate(i);
//		}
//		System.out.print("Decs: ");
//		for(int i =0; i < decorations.size();i++) {
//			System.out.print(decorations.get(i).size()+"; ");
//		}System.out.println();
//		System.out.println("StdDevBlocks: "+this.getStdDevOfBlocks());
	}
	
	private int buildBlock(Block block, int xo, int maxLength, int blockIndex) {
		if(block.blockType == 0) {
			return buildJump(xo, maxLength, false);
		}
		else if(block.blockType == 1) {
			return buildCannons(xo, maxLength, false);
		}
		else if(block.blockType == 2) {
			return buildHillStraight(xo, maxLength, blockIndex, false);
		}
		else if(block.blockType == 3) {
			return buildTubes(xo, maxLength, false);
		}
		else if(block.blockType == 4) {
			return buildStraight(xo, maxLength, blockIndex, false, false);
		}
		else
			return 0;
	}
	
	private int buildRandomBlock(int xo, int maxLength, int blockIndex, boolean staticLength) {
		double rand = random.nextDouble();
		
		//Fix for replacing a block with a hill when it's too short to fit one
		if(maxLength<10 && rand>cannonWeight && rand<=hillWeight) {
			while(rand>cannonWeight && rand<=hillWeight)
				rand = random.nextDouble();
		}
		
		Block newBlock = new Block();
		newBlock.blockStart = xo;
		
		if(rand <= jumpWeight) {
			newBlock.blockType=0;
			newBlock.blockLength = buildJump(xo, maxLength, staticLength);
		}
		else if(rand <= cannonWeight) {
			newBlock.blockType=1;
			newBlock.blockLength = buildCannons(xo, maxLength, staticLength);
		}
		else if(rand <= hillWeight) {
			newBlock.blockType=2;
			newBlock.blockLength = buildHillStraight(xo, maxLength, blockIndex, staticLength);
		}
		else if(rand <= tubeWeight) {
			newBlock.blockType=3;
			newBlock.blockLength = buildTubes(xo, maxLength, staticLength);
		}
		else if(rand <= straightWeight) {
			newBlock.blockType=4;
			newBlock.blockLength = buildStraight(xo, maxLength, blockIndex, staticLength, false);
		}
		else {
			return -1; //really shouldn't get here
		}
		
		if(blockIndex == blocks.size())
			blocks.add(newBlock);
		else {
			blocks.remove(blockIndex);
			blocks.add(blockIndex, newBlock);
		}
			
		return newBlock.blockLength;
	}


	double jumpWeight, cannonWeight, hillWeight, tubeWeight, straightWeight;    

	private void constructWeights(GamePlay playerMetrics) {
		//calculate weights based on stuff
		//weight = baseChance + (chanceIncrement * playersOffset);
		
		int totalKills = playerMetrics.ArmoredTurtlesKilled +
			playerMetrics.ChompFlowersKilled +
			playerMetrics.GoombasKilled + 
			playerMetrics.GreenTurtlesKilled +
			playerMetrics.RedTurtlesKilled +
			playerMetrics.CannonBallKilled;

		jumpWeight = 	1 + 0.1 * (playerMetrics.jumpsNumber-22) - 0.75 * playerMetrics.percentageBlocksDestroyed;
		cannonWeight = 	0.5 + 0.08 * totalKills + 0.005 * playerMetrics.completionTime + 0.2 * playerMetrics.CannonBallKilled - 0.75 * playerMetrics.timesOfDeathByCannonBall;
		hillWeight =	4 + 0.25 * playerMetrics.aimlessJumps + 0.2 * totalKills;
		tubeWeight = 	2 + 1 * playerMetrics.ChompFlowersKilled - 0.5 * playerMetrics.timesOfDeathByJumpFlower;
		straightWeight = 10 + 0.05 * playerMetrics.timeRunningRight + 0.2 * totalKills + 0.5 * playerMetrics.kickedShells;

		//normalize weights
		double sum = jumpWeight + cannonWeight + hillWeight + tubeWeight + straightWeight;
		jumpWeight/=sum;
		cannonWeight/=sum;
		hillWeight/=sum;
		tubeWeight/=sum;
		straightWeight/=sum;

		//offset weights to cover [0,1]
		cannonWeight += jumpWeight;
		hillWeight += cannonWeight;
		tubeWeight += hillWeight;
		straightWeight += tubeWeight; //straightWeight should = 1 now
	}

	private int buildJump(int xo, int maxLength, boolean staticLength)
	{	gaps++;
	//jl: jump length
	//js: the number of blocks that are available at either side for free
	int js = random.nextInt(4) + 2;
	int jl = random.nextInt(2) + 2;
	int length = js * 2 + jl;
	if (staticLength) {
		length = maxLength;
		if(maxLength%2==0)
			jl = 2;
		else
			jl = 3;
		js = (maxLength - jl)/2;
	}

	boolean hasStairs = random.nextInt(3) == 0;

	int floor = height - 1 - random.nextInt(4);
	//run from the start x position, for the whole length
	for (int x = xo; x < xo + length; x++)
	{
		if (x < xo + js || x > xo + length - js - 1)
		{
			//run for all y's since we need to paint blocks upward
			for (int y = 0; y < height; y++)
			{	//paint ground up until the floor
				if (y >= floor)
				{
					setBlock(x, y, GROUND);
				}
				//if it is above ground, start making stairs of rocks
				else if (hasStairs)
				{	//LEFT SIDE
					if (x < xo + js)
					{ //we need to max it out and level because it wont
						//paint ground correctly unless two bricks are side by side
						if (y >= floor - (x - xo) + 1)
						{
							setBlock(x, y, ROCK);
						}
					}
					else
					{ //RIGHT SIDE
						if (y >= floor - ((xo + length) - x) + 2)
						{
							setBlock(x, y, ROCK);
						}
					}
				}
			}
		}
	}

	return length;
	}

	private int buildCannons(int xo, int maxLength, boolean staticLength)
	{
		int length = random.nextInt(10) + 2;
		if (length > maxLength || staticLength) length = maxLength;

		int floor = height - 1 - random.nextInt(4);
		int xCannon = xo + 1 + random.nextInt(4);
		for (int x = xo; x < xo + length; x++)
		{
			if (x > xCannon)
			{
				xCannon += 2 + random.nextInt(4);
			}
			if (xCannon == xo + length - 1) xCannon += 10;
			int cannonHeight = floor - random.nextInt(4) - 1;

			for (int y = 0; y < height; y++)
			{
				if (y >= floor)
				{
					setBlock(x, y, GROUND);
				}
				else
				{
					if (x == xCannon && y >= cannonHeight)
					{
						if (y == cannonHeight)
						{
							setBlock(x, y, (byte) (14 + 0 * 16));
						}
						else if (y == cannonHeight + 1)
						{
							setBlock(x, y, (byte) (14 + 1 * 16));
						}
						else
						{
							setBlock(x, y, (byte) (14 + 2 * 16));
						}
					}
				}
			}
		}

		return length;
	}

	private int buildHillStraight(int xo, int maxLength, int blockIndex, boolean staticLength)
	{
		int length = random.nextInt(10) + 10;
		if (length > maxLength || staticLength) length = maxLength;

		int floor = height - 1 - random.nextInt(4);
		for (int x = xo; x < xo + length; x++)
		{
			for (int y = 0; y < height; y++)
			{
				if (y >= floor)
				{
					setBlock(x, y, GROUND);
				}
			}
		}

		addEnemyLine(xo + 1, xo + length - 1, floor - 1, blockIndex);

		int h = floor;

		boolean keepGoing = true;

		boolean[] occupied = new boolean[length];
		while (keepGoing)
		{
			h = h - 2 - random.nextInt(3);

			if (h <= 0)
			{
				keepGoing = false;
			}
			else
			{
				int l = random.nextInt(5) + 3;
				int xxo = random.nextInt(length - l - 2) + xo + 1;

				if (occupied[xxo - xo] || occupied[xxo - xo + l] || occupied[xxo - xo - 1] || occupied[xxo - xo + l + 1])
				{
					keepGoing = false;
				}
				else
				{
					occupied[xxo - xo] = true;
					occupied[xxo - xo + l] = true;
					addEnemyLine(xxo, xxo + l, h - 1, blockIndex);
					if (random.nextInt(4) == 0)
					{
						decorate(xxo - 1, xxo + l + 1, h, blockIndex);
						keepGoing = false;
					}
					for (int x = xxo; x < xxo + l; x++)
					{
						for (int y = h; y < floor; y++)
						{
							int xx = 5;
							if (x == xxo) xx = 4;
							if (x == xxo + l - 1) xx = 6;
							int yy = 9;
							if (y == h) yy = 8;

							if (getBlock(x, y) == 0)
							{
								setBlock(x, y, (byte) (xx + yy * 16));
							}
							else
							{
								if (getBlock(x, y) == HILL_TOP_LEFT) setBlock(x, y, HILL_TOP_LEFT_IN);
								if (getBlock(x, y) == HILL_TOP_RIGHT) setBlock(x, y, HILL_TOP_RIGHT_IN);
							}
						}
					}
				}
			}
		}

		return length;
	}

	private void addEnemyLine(int x0, int x1, int y, int blockIndex)
	{ 
		//	    	public static final int ENEMY_RED_KOOPA = 0;
		//	    public static final int ENEMY_GREEN_KOOPA = 1;
		//	    public static final int ENEMY_GOOMBA = 2;
		//	    public static final int ENEMY_SPIKY = 3;
		//	    public static final int ENEMY_FLOWER = 4;
		for (int x = x0; x < x1; x++)
		{
			if (random.nextInt(35) < difficulty + 1)
			{
				int type = random.nextInt(4);

				if (difficulty < 1)
				{
					type = Enemy.ENEMY_GOOMBA;
				}
				else if (difficulty < 3)
				{
					type = random.nextInt(3);
				}

				setSpriteTemplate(x, y, new SpriteTemplate(type, random.nextInt(35) < difficulty));
				ENEMIES++;
				recordEnemy(x, y, blockIndex, type);
			}
		}
	}

	private int buildTubes(int xo, int maxLength, boolean staticLength)
	{
		int length = random.nextInt(10) + 5;
		if (length > maxLength || staticLength) length = maxLength;

		int floor = height - 1 - random.nextInt(4);
		int xTube = xo + 1 + random.nextInt(4);
		int tubeHeight = floor - random.nextInt(2) - 2;
		for (int x = xo; x < xo + length; x++)
		{
			if (x > xTube + 1)
			{
				xTube += 3 + random.nextInt(4);
				tubeHeight = floor - random.nextInt(2) - 2;
			}
			if (xTube >= xo + length - 2) xTube += 10;

			if (x == xTube && random.nextInt(11) < difficulty + 1)
			{
				setSpriteTemplate(x, tubeHeight, new SpriteTemplate(Enemy.ENEMY_FLOWER, false));
				ENEMIES++;
			}

			for (int y = 0; y < height; y++)
			{
				if (y >= floor)
				{
					setBlock(x, y,GROUND);

				}
				else
				{
					if ((x == xTube || x == xTube + 1) && y >= tubeHeight)
					{
						int xPic = 10 + x - xTube;

						if (y == tubeHeight)
						{
							//tube top
							setBlock(x, y, (byte) (xPic + 0 * 16));
						}
						else
						{
							//tube side
							setBlock(x, y, (byte) (xPic + 1 * 16));
						}
					}
				}
			}
		}

		return length;
	}

	private int buildStraight(int xo, int maxLength, int blockIndex, boolean staticLength, boolean safe)
	{
		int length = random.nextInt(10) + 2;
	      
		if (safe)
			length = 10 + random.nextInt(5);

		if (length > maxLength || staticLength)
			length = maxLength;


		int floor = height - 1 - random.nextInt(4);

		//runs from the specified x position to the length of the segment
		for (int x = xo; x < xo + length; x++)
		{
			for (int y = 0; y < height; y++)
			{
				if (y >= floor)
				{
					setBlock(x, y, GROUND);
				}
			}
		}

		if (!safe)
		{
			if (length > 5)
			{
				decorate(xo, xo + length, floor, blockIndex);
			}
		}

		return length;
	}

	private void decorate(int xStart, int xLength, int floor, int blockIndex)
	{
		//if its at the very top, just return
		if (floor < 1)
			return;

		//        boolean coins = random.nextInt(3) == 0;
		boolean rocks = true;

		//add an enemy line above the box
		addEnemyLine(xStart + 1, xLength - 1, floor - 1, blockIndex);

		int s = random.nextInt(4);
		int e = random.nextInt(4);

		if (floor - 2 > 0){
			if ((xLength - 1 - e) - (xStart + 1 + s) > 1){
				for(int x = xStart + 1 + s; x < xLength - 1 - e; x++){
					setBlock(x, floor - 2, COIN);
					COINS++;
					recordCoin(x, floor - 2, blockIndex, false);
				}
			}
		}

		s = random.nextInt(4);
		e = random.nextInt(4);

		//this fills the set of blocks and the hidden objects inside them
		if (floor - 4 > 0)
		{
			if ((xLength - 1 - e) - (xStart + 1 + s) > 2)
			{
				for (int x = xStart + 1 + s; x < xLength - 1 - e; x++)
				{
					if (rocks)
					{
						if (x != xStart + 1 && x != xLength - 2 && random.nextInt(3) == 0)
						{
							if (random.nextInt(4) == 0)
							{
								setBlock(x, floor - 4, BLOCK_POWERUP);
								BLOCKS_POWER++;
								recordPowerup(x, floor - 4, blockIndex);
							}
							else
							{	//the fills a block with a hidden coin
								setBlock(x, floor - 4, BLOCK_COIN);
								BLOCKS_COINS++;
								recordCoin(x, floor - 4, blockIndex, true);
							}
						}
						else if (random.nextInt(4) == 0)
						{
							if (random.nextInt(4) == 0)
							{
								setBlock(x, floor - 4, (byte) (2 + 1 * 16));
								BLOCKS_POWER++;
								recordPowerup(x, floor - 4, blockIndex);
							}
							else
							{
								setBlock(x, floor - 4, (byte) (1 + 1 * 16));
								BLOCKS_COINS++;
								recordCoin(x, floor - 4, blockIndex, true);
							}
						}
						else
						{
							setBlock(x, floor - 4, BLOCK_EMPTY);
							BLOCKS_EMPTY++;
							recordEmptyBlock(x, floor - 4, blockIndex);
						}
					}
				}
			}
		}
	}

    public void undecorate(int blockIndex) {
    	for(Decoration dec : decorations.get(blockIndex)) {
    		if(dec.type != 0) //don't do this if its an enemy
    			setBlock(dec.x, dec.y, (byte) 0);
    		else {
    			//remove the enemy
    			removeSpriteTemplate(dec.x, dec.y);
    		}
    	}
    	decorations.get(blockIndex).clear();
    }

	private void fixWalls(int xStart, int xEnd)
	{
		boolean[][] blockMap = new boolean[width + 1][height + 1];

		for (int x = xStart; x < xEnd + 1; x++)
		{
			for (int y = 0; y < height + 1; y++)
			{
				int blocks = 0;
				for (int xx = x - 1; xx < x + 1; xx++)
				{
					for (int yy = y - 1; yy < y + 1; yy++)
					{
						if (getBlockCapped(xx, yy) == GROUND){
							blocks++;
						}
					}
				}
				blockMap[x][y] = blocks == 4;
			}
		}
		blockify(this, blockMap, xStart, xEnd + 1, height + 1);
	}

	private void blockify(Level level, boolean[][] blocks, int xStart, int xEnd, int height){
		int to = 0;
		if (type == LevelInterface.TYPE_CASTLE)
		{
			to = 4 * 2;
		}
		else if (type == LevelInterface.TYPE_UNDERGROUND)
		{
			to = 4 * 3;
		}

		boolean[][] b = new boolean[2][2];

		for (int x = xStart; x < xEnd; x++)
		{
			for (int y = 0; y < height; y++)
			{
				for (int xx = x; xx <= x + 1; xx++)
				{
					for (int yy = y; yy <= y + 1; yy++)
					{
						int _xx = xx;
						int _yy = yy;
						if (_xx < 0) _xx = 0;
						if (_yy < 0) _yy = 0;
						if (_xx > width - 1) _xx = width - 1;
						if (_yy > height - 1) _yy = height - 1;
						b[xx - x][yy - y] = blocks[_xx][_yy];
					}
				}

				if (b[0][0] == b[1][0] && b[0][1] == b[1][1])
				{
					if (b[0][0] == b[0][1])
					{
						if (b[0][0])
						{
							level.setBlock(x, y, (byte) (1 + 9 * 16 + to));
						}
						else
						{
							// KEEP OLD BLOCK!
						}
					}
					else
					{
						if (b[0][0])
						{
							//down grass top?
									level.setBlock(x, y, (byte) (1 + 10 * 16 + to));
						}
						else
						{
							//up grass top
							level.setBlock(x, y, (byte) (1 + 8 * 16 + to));
						}
					}
				}
				else if (b[0][0] == b[0][1] && b[1][0] == b[1][1])
				{
					if (b[0][0])
					{
						//right grass top
						level.setBlock(x, y, (byte) (2 + 9 * 16 + to));
					}
					else
					{
						//left grass top
						level.setBlock(x, y, (byte) (0 + 9 * 16 + to));
					}
				}
				else if (b[0][0] == b[1][1] && b[0][1] == b[1][0])
				{
					level.setBlock(x, y, (byte) (1 + 9 * 16 + to));
				}
				else if (b[0][0] == b[1][0])
				{
					if (b[0][0])
					{
						if (b[0][1])
						{
							level.setBlock(x, y, (byte) (3 + 10 * 16 + to));
						}
						else
						{
							level.setBlock(x, y, (byte) (3 + 11 * 16 + to));
						}
					}
					else
					{
						if (b[0][1])
						{
							//right up grass top
							level.setBlock(x, y, (byte) (2 + 8 * 16 + to));
						}
						else
						{
							//left up grass top
							level.setBlock(x, y, (byte) (0 + 8 * 16 + to));
						}
					}
				}
				else if (b[0][1] == b[1][1])
				{
					if (b[0][1])
					{
						if (b[0][0])
						{
							//left pocket grass
							level.setBlock(x, y, (byte) (3 + 9 * 16 + to));
						}
						else
						{
							//right pocket grass
							level.setBlock(x, y, (byte) (3 + 8 * 16 + to));
						}
					}
					else
					{
						if (b[0][0])
						{
							level.setBlock(x, y, (byte) (2 + 10 * 16 + to));
						}
						else
						{
							level.setBlock(x, y, (byte) (0 + 10 * 16 + to));
						}
					}
				}
				else
				{
					level.setBlock(x, y, (byte) (0 + 1 * 16 + to));
				}
			}
		}
	}

	public MyLevel perturb(GamePlay playerMetrics) {

		int blockIndex = random.nextInt(blocks.size());
		Block block = blocks.get(blockIndex);
		if(random.nextInt(10) > 5) {
			
			block.blockType = random.nextInt(4);
		}
		else {
			undecorate(blockIndex);
			decorate(block.blockStart, block.blockLength, 14, blockIndex);
		}
		
		if(random.nextInt(8) == 1) {
			addEnemyLine(block.blockStart, block.blockStart+block.blockLength, 2, blockIndex);
		}
		
		MyLevel level = new MyLevel(width, height, seed, difficulty, type, playerMetrics, 
				cloneBlocks(), cloneDecs());
		return level;
	}
	
	private ArrayList<Block> cloneBlocks() {
		ArrayList<Block> newBlocks = new ArrayList<Block>();
		for(Block b : blocks) {
			Block newBlock = new Block();
			newBlock.blockStart = b.blockStart;
			newBlock.blockLength = b.blockLength;
			newBlock.blockType = b.blockType;
			newBlocks.add(newBlock);
		}
		
		return newBlocks;
	}
	
	private ArrayList<ArrayList<Decoration>> cloneDecs() {
		ArrayList<ArrayList<Decoration>> newDecs = new ArrayList<ArrayList<Decoration>>();
		
		for(ArrayList<Decoration> decList : decorations) {
			
			ArrayList<Decoration> newDecList = new ArrayList<Decoration>();
			
			for(Decoration d : decList) {
				Decoration newDec = new Decoration();
				
				newDec.type = d.type;
				newDec.x = d.x;
				newDec.y = d.y;
				newDec.enemyType = d.enemyType;
				newDec.blockType = d.blockType;				
			}
			newDecs.add(newDecList);
		}
		
		return newDecs;
	}

	public MyLevel clone() throws CloneNotSupportedException {

		MyLevel clone=new MyLevel(width, height);

		clone.xExit = xExit;
		clone.yExit = yExit;
		byte[][] map = getMap();
		SpriteTemplate[][] st = getSpriteTemplate();

		for (int i = 0; i < map.length; i++)
			for (int j = 0; j < map[i].length; j++) {
				clone.setBlock(i, j, map[i][j]);
				clone.setSpriteTemplate(i, j, st[i][j]);
			}
		clone.BLOCKS_COINS = BLOCKS_COINS;
		clone.BLOCKS_EMPTY = BLOCKS_EMPTY;
		clone.BLOCKS_POWER = BLOCKS_POWER;
		clone.ENEMIES = ENEMIES;
		clone.COINS = COINS;
		
		clone.blocks = blocks;
		clone.decorations = decorations;

		return clone;

	}

	private int getDifficulty(GamePlay player) {	  

		//map percentage of time allowed to a [1-10] difficulty
		int time = 5 * (1- (player.completionTime / player.totalTime)); //use a max of 5 since it's still really hard

		double deaths = 
			player.timesOfDeathByArmoredTurtle + 
			player.timesOfDeathByCannonBall + 
			player.timesOfDeathByChompFlower + 
			player.timesOfDeathByFallingIntoGap +
			player.timesOfDeathByGoomba +
			player.timesOfDeathByGreenTurtle +
			player.timesOfDeathByJumpFlower + 
			player.timesOfDeathByRedTurtle;


		return time;
	}


	//eval 
	//unsimilar neighbor blocks
	//compare enemies to wanted enemies totals
	//compare coins ...
	public double eval(GamePlay player) {

		//tends to be around 3-5 range
		double blockStdDev = getStdDevOfBlocks();
		int blockPenalty = getBlockNeighborPenalty();
		double enemyStdDev = getStdDevOfEnemies();
		double enemyDifference = getEnemyDifference(player);
		double coinDifference = getCoinDifference(player);
		
		return blockStdDev*0.6 + enemyStdDev + blockPenalty/3 + enemyDifference + (coinDifference/3);
	}
	
	private int getBlockNeighborPenalty() {
		int penalty = 0;
		double multiplier = 1;
		for(int i=1; i<blocks.size(); i++) {
			if(blocks.get(i).blockType == blocks.get(i-1).blockType) {
				penalty+=multiplier;
				multiplier*=2;
			}
			else
				multiplier=1;
		}
		
		return penalty;
	}

	private double getStdDevOfBlocks() {	    	
		int numJumps = getNumOfBlockType(0);
		int numCannons = getNumOfBlockType(1);
		int numHills = getNumOfBlockType(2);
		int numTubes = getNumOfBlockType(3);
		int numStraights = getNumOfBlockType(4);

		double mean = (numJumps + numCannons + numHills + numTubes + numStraights) / 5;

		double stdDev = Math.pow(mean - numJumps, 2) + 
		Math.pow(mean - numCannons, 2) + 
		Math.pow(mean - numHills, 2) + 
		Math.pow(mean - numTubes, 2) + 
		Math.pow(mean - numStraights, 2);
		stdDev /= 5;
		stdDev = Math.sqrt(stdDev);

		return stdDev;
	}

	private double getStdDevOfEnemies() {    	
		int numGoombas = getNumOfEnemyType(Enemy.ENEMY_GOOMBA);
		int numGreenKoopas = getNumOfEnemyType(Enemy.ENEMY_GREEN_KOOPA);
		int numRedKoopas = getNumOfEnemyType(Enemy.ENEMY_RED_KOOPA);

		double mean = (numGoombas + numGreenKoopas + numRedKoopas) / 3;

		double stdDev = Math.pow(mean - numGoombas, 2) + 
		Math.pow(mean - numGreenKoopas, 2) + 
		Math.pow(mean - numRedKoopas, 2);
		stdDev /= 3;
		stdDev = Math.sqrt(stdDev);

		return stdDev;
	}

	private int getNumOfEnemyType(int type) {
		int count = 0;

		for(ArrayList<Decoration> decs : decorations) {
			for(Decoration dec : decs) {
				if(dec.type == 0 && dec.enemyType == type)
					count++;
			}
		}

		return count;
	}

	private int getNumOfBlockType(int type) {
		int count = 0;
		for(Block b : blocks) {
			if(b.blockType == type)
				count ++;
		}

		return count;
	}
	private int getNumCoinsInBlocks() {
		int count = 0;

		for(ArrayList<Decoration> decs : decorations) {
			for(Decoration dec : decs) {
				if(dec.type == 2 && dec.blockType == 0)
					count++;
			}
		}

		return count;
	}
	private int getNumCoinsNotInBlocks() {

		int count = 0;

		for(ArrayList<Decoration> decs : decorations) {
			for(Decoration dec : decs) {
				if(dec.type == 1)
					count++;
			}
		}

		return count;
	}


	private double getEnemyDifference(GamePlay player) {
		
		int maxGoombas = 20;
		int maxTurtles = 25;
		int enemyDeaths = player.timesOfDeathByGoomba +
				player.timesOfDeathByGreenTurtle +
				player.timesOfDeathByRedTurtle +
				player.timesOfDeathByArmoredTurtle;
		
		double idealGoombas = 3+(player.GoombasKilled / 8.) * maxGoombas - enemyDeaths*2;
		double idealTurtles = 4+((player.RedTurtlesKilled + player.GreenTurtlesKilled) / 8.) * maxTurtles - enemyDeaths*2;
		
		//get the difference in the number of enemies there should be
		int numGoombas = getNumOfEnemyType(Enemy.ENEMY_GOOMBA);
		int numGreenKoopas = getNumOfEnemyType(Enemy.ENEMY_GREEN_KOOPA);
		int numRedKoopas = getNumOfEnemyType(Enemy.ENEMY_RED_KOOPA);

		return 
		Math.abs(idealGoombas - numGoombas) + 
		Math.abs(idealTurtles - (numGreenKoopas + numRedKoopas));	    	
	}    
	private double getCoinDifference(GamePlay player) {	 
		
		final int maxCoinBlocks = 20;
		final int maxCoins = 70;
		
		double idealCoins, idealBlocks;
		idealBlocks = player.percentageCoinBlocksDestroyed * maxCoinBlocks;
		idealCoins = 1.0*(player.coinsCollected / player.totalCoins) * maxCoins;
		
		return Math.abs(idealCoins - getNumCoinsNotInBlocks()) + 
		Math.abs(idealBlocks - getNumCoinsInBlocks())*0.75;
	}
	
	public class Block {
		public int blockType;
		public int blockStart;
		public int blockLength;
	}

	public class Decoration {
		public int x, y;

		/**
		 * 0 - enemy
		 * 1 - coin
		 * 2 - block
		 * 
		 */
		public int type;

		/**
		 * 0 - Goomba
		 * 1 - 
		 */
		public int enemyType;


		/**
		 * 0 - coin
		 * 1 - power up
		 * 2 - empty
		 */
		public int blockType;
	}

	private void recordCoin(int x, int y, int blockIndex, boolean inBlock) {
		Decoration coin = new Decoration();
		coin.x = x;
		coin.y = y;
		if(inBlock) {
			coin.type = 2;
			coin.blockType = 0;
		}
		else {
			coin.type = 1;
		}

		decorations.get(blockIndex).add(coin);
	}

	private void recordPowerup(int x, int y, int blockIndex){
		Decoration block = new Decoration();
		block.x = x;
		block.y = y;
		block.type = 2;
		block.blockType = 1;

		decorations.get(blockIndex).add(block);	    	
	}

	private void recordEmptyBlock(int x, int y, int blockIndex) {
		Decoration block = new Decoration();
		block.x = x;
		block.y = y;
		block.type = 2;
		block.blockType = 2;

		decorations.get(blockIndex).add(block);	    	
	}

	private void recordEnemy(int x, int y, int blockIndex, int type) {
		Decoration enemy = new Decoration();
		enemy.x = x;
		enemy.y = y;
		enemy.type = 0;
		enemy.enemyType = type;

		decorations.get(blockIndex).add(enemy);	

	}
}


